package ar.uba.fi.tonyvaliente.tree;

import java.util.logging.Logger;

import ar.uba.fi.tonyvaliente.files.File;
import ar.uba.fi.tonyvaliente.files.FileBlock;
import ar.uba.fi.tonyvaliente.files.FileException;
import ar.uba.fi.tonyvaliente.files.FileRecord;
import ar.uba.fi.tonyvaliente.files.Persistor;
import ar.uba.fi.tonyvaliente.files.PersistorException;
import ar.uba.fi.tonyvaliente.files.RecordPosition;

/**
 * Clase que envuelve un archivo de bloques (File) donde hay un arbol B+
 * y realiza la serializacion y deserializacion de los nodos del mismo.
 * @author Diego Morello
 * @param <K> Clase a la cual deben pertenecer las claves, debe ser comparable
 * @param <V> Clase a la cual deben pertenecer los valores
 */
public class BPlusTreeFileWrapper<K extends Comparable<K>, V> {
	
	private static Logger log = Logger.getLogger(BPlusTreeFileWrapper.class.getName());
	
	private File file;
	private NodePersistor<K,V> nodePersistor;
	
	/**
	 * Crea una nueva instancia de la clase que apunta a un archivo de bloques
	 * donde hay un arbol B+ y sabe como serializar y deserializar las claves y valores del arbol 
	 * @param file Archivo de bloques donde se encuentra el arbol B+
	 * @param keyPersistor Persistor para serializar y deserializar las claves del arbol
	 * @param valuePersistor Persistor para serializar y deserializar los valores del arbol
	 */
	public BPlusTreeFileWrapper(File file, Persistor<K> keyPersistor, Persistor<V> valuePersistor) {
		this.file = file;
		this.nodePersistor = new NodePersistor<K,V>(keyPersistor, valuePersistor, this); 
	}
	
	/**
	 * Cierra el archivo encapsulado
	 * @throws BPlusTreeException si ocurre un error al cerrar el archivo
	 */
	public void close() throws BPlusTreeException {
		try {
			file.close();
		} catch (FileException e) {
			throw new BPlusTreeException("Error al cerrar el BPlusTreeFileWrapper", e);
		}
	}
	
	/**
	 * Obtiene el nodo raiz del arbol. Para ello, busca en el File subyacente las propiedades
	 * rootNodeBlockNumber y rootNodeOffset, que indican el bloque y offset dentro del bloque
	 * donde se encuentra el FileRecord correspondiente al nodo raiz.
	 */
	public RecordPosition getRootNodePosition() throws BPlusTreeException {
		String strRootBlockNumber = file.getProperty("rootNodeBlockNumber");
		String strRootOffset = file.getProperty("rootNodeOffset");
		
		if (strRootBlockNumber != null && strRootOffset != null) {
			try {
				int rootBlockNumber = Integer.parseInt(strRootBlockNumber);
				int rootOffset = Integer.parseInt(strRootOffset);
				return new RecordPosition(rootBlockNumber, rootOffset);
			} catch (NumberFormatException e) {
				throw new BPlusTreeException("Error al obtener posicion del nodo raiz: "
						+ "valor incorrecto", e);
			}
		} else {
			throw new BPlusTreeException("Error en el archivo de arbol: "
					+ "no se encuentra la posicion del nodo raiz");
		}
	}
	
	/**
	 * Asigna al archivo la nueva posicion del nodo raiz
	 * @param rootNode
	 * @throws BPlusTreeException
	 */
	public void setRootNodePosition(RecordPosition pos) throws BPlusTreeException {
		try {
			log.fine("setRootNode IN");
			log.finer("rootNodeBlockNumber: " + pos.getBlockNumber());
			log.finer("rootNodeOffset: " + pos.getOffset());
			
			file.setProperty("rootNodeBlockNumber", Integer.toString(pos.getBlockNumber()));
			file.setProperty("rootNodeOffset", Integer.toString(pos.getOffset()));
		} catch (FileException e) {
			throw new BPlusTreeException("Error al asignar nuevo nodo raiz", e);
		}finally{
			try {
				file.flush();
			} catch (FileException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Obtiene el nodo cuyo bloque y offset dentro del bloque recibe como parametro.
	 * 
	 * @param blockNumber posicion del bloque dentro del File
	 * @param offset posicion del nodo serializado dentro del bloque
	 * @return nodo recuperado
	 */
	public BPlusTreeNode<K, V> getNode(int blockNumber, int offset) throws BPlusTreeException {
		try {
			log.fine("getNode IN");
			log.finer("BlockNumber: " + blockNumber);
			log.finer("NodeOffset: " + offset);
			
			FileBlock block = file.getBlock(blockNumber);
			FileRecord record = block.getRecord(offset);
			return nodePersistor.deserialize(record.getData());
		} catch (FileException e) {
			throw new BPlusTreeException("Error al acceder al archivo para obtener nodo", e);
		} catch (PersistorException e) {
			throw new BPlusTreeException("Error al deserializar nodo", e);
		}
	}
	
	/**
	 * Agrega un nodo al archivo.
	 * @param node nodo a agregar
	 * @return RecordPosition que contiene el numero de bloque y offset
	 * donde se escribio el nodo
	 */
	public RecordPosition appendNode(BPlusTreeNode<K,V> node) throws BPlusTreeException {
		try {
			
			log.fine("appendNode IN");
			try{
				log.finer("node: " + node);
			}catch(Exception e){
				
			}
			
			FileRecord r = new FileRecord(nodePersistor.serialize(node));
			RecordPosition posicion = file.addRecord(r);
			log.finer("posicion: block: " + posicion.getBlockNumber() + " offset:  " + posicion.getOffset());
			
			return posicion;
		} catch (FileException e) {
			throw new BPlusTreeException("Error al acceder al archivo para agregar nodo", e);
		} catch (PersistorException e) {
			throw new BPlusTreeException("Error al serializar nodo para agregar en archivo", e);
		}/*finally{
			try {
				file.flush();
			} catch (FileException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}*/
	}
	
	/**
	 * Escribe un nodo en el archivo, en la posicion indicada por parametro
	 * @param node Nodo a actualizar en archivo
	 * @param pos Posicion del archivo donde escribir el nodo
	 * @return TRUE si pudo escribir el nodo, FALSE si el bloque indicado en pos estaba lleno
	 * y no pudo hacerse la actualizacion
	 * @throws BPlusTreeException Si ocurre un error al acceder a archivo o serializar el nodo
	 */
	public boolean updateNode(BPlusTreeNode<K,V> node, RecordPosition pos) throws BPlusTreeException {
		try {
			
			log.fine("updateRootNode IN");
			log.finer("NodeBlockNumber: " + pos.getBlockNumber());
			log.finer("NodeOffset: " + pos.getOffset());
			try{
				log.finer("node: " + node);
			}catch(Exception e){
				
			}
			
			FileBlock block = file.getBlock(pos.getBlockNumber());
			FileRecord r = new FileRecord(nodePersistor.serialize(node));
			block.updateRecord(pos.getOffset(), r);
			file.updateBlock(pos.getBlockNumber(), block);
			return true;
		} catch (FileException e) {
			throw new BPlusTreeException("Error al acceder a archivo para actualizar nodo", e);
		} catch (PersistorException e) {
			throw new BPlusTreeException("Error al serializar nodo para actualizar en disco", e);
		}/*finally{
			try {
				file.flush();
			} catch (FileException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}*/
	}
	
	/**
	 * Setea la posición del primer nodo del secuencial set.
	 * 
	 * @param first
	 * @throws BPlusTreeException 
	 */
	public void setFirstNodePosition(RecordPosition first) throws BPlusTreeException{
		try{
			this.file.setProperty("firstNodeBlock", Integer.toString(first.getBlockNumber()));
			this.file.setProperty("firstNodeOffset", Integer.toString(first.getOffset()));
		}catch (FileException e) {
			throw new BPlusTreeException("Error al asignar nuevo first node", e);
		}finally{
			try {
				file.flush();
			} catch (FileException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Devuelve la posición del primer nodo del secuencial set.
	 * 
	 * @return
	 * @throws BPlusTreeException
	 */
	public RecordPosition getFirstNodePosition() throws BPlusTreeException {
		String strRootBlockNumber = file.getProperty("firstNodeBlock");
		String strRootOffset = file.getProperty("firstNodeOffset");
		
		if (strRootBlockNumber != null && strRootOffset != null) {
			try {
				int rootBlockNumber = Integer.parseInt(strRootBlockNumber);
				int rootOffset = Integer.parseInt(strRootOffset);
				return new RecordPosition(rootBlockNumber, rootOffset);
			} catch (NumberFormatException e) {
				throw new BPlusTreeException("Error al obtener posicion del first node: "
						+ "valor incorrecto", e);
			}
		} else {
			throw new BPlusTreeException("Error en el archivo de arbol: "
					+ "no se encuentra la posicion del first node");
		}
	}

	public void flush() {
		try {
			this.file.flush();
		} catch (FileException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
